Skip to content

API integration test#232

Closed
jenstopp wants to merge 21 commits intomainfrom
test/api-integration-test
Closed

API integration test#232
jenstopp wants to merge 21 commits intomainfrom
test/api-integration-test

Conversation

@jenstopp
Copy link
Copy Markdown
Collaborator

@jenstopp jenstopp commented Mar 3, 2026

Add a provider-tests crate that exercises the azihsm provider through
the OpenSSL EVP API (not the CLI tool), enabling testing of session-based
ephemeral keys that cannot be tested via the command line.

Tests added:

  • SmokeTest: provider loads and reports correct name
  • EC session key sign/verify round trip (P-256, P-384, P-521)
  • RSA session key sign/verify (PKCS#1 v1.5 and PSS padding)
  • RSA session key encrypt/decrypt (OAEP)
  • ECDH session key derivation (to buffer and to file)
  • Negative tests: cross-key failures, tampered data, mismatched curves

Additional coverage added in follow-up commits:

  • Digest streaming tests (SHA-256, SHA-384, SHA-512)
  • HMAC tests (SHA-256, SHA-384, SHA-512) using ECDH-derived masked keys
  • HKDF key derivation tests (with/without salt and info parameters)
  • ECDH → HKDF → HMAC end-to-end round trip

Copilot AI review requested due to automatic review settings March 3, 2026 22:05
@jenstopp jenstopp marked this pull request as draft March 3, 2026 22:06
@jenstopp
Copy link
Copy Markdown
Collaborator Author

jenstopp commented Mar 3, 2026

The work is branched from the other not yet merged integration test PR #191
The API based test is just the last commit.

Will update once #191 is merged.

@jenstopp jenstopp linked an issue Mar 3, 2026 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new integration test coverage for the AZIHSM OpenSSL provider, including a new C++/EVP-based test crate for session-only keys and a Rust/lit-based shell-script suite, plus CI wiring to run them.

Changes:

  • Add provider-tests crate that builds/runs C++ GoogleTest cases exercising the provider via the OpenSSL EVP API.
  • Add integration-tests crate to run existing/new CLI-oriented provider tests via lit and bash scripts.
  • Update CI and xtask tooling to run the new integration suites; adjust RSA-PSS saltlen handling in the provider.

Reviewed changes

Copilot reviewed 60 out of 60 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
xtask/src/main.rs Registers a new xtask command for integration tests.
xtask/src/integration_tests.rs Adds an xtask to run the integration-tests crate.
xtask/src/coverage.rs Updates workspace coverage invocation to exclude integration-tests.
provider-tests/cpp/utils/provider_ctx.hpp RAII OpenSSL provider/libctx loader used by C++ tests.
provider-tests/cpp/utils/ossl_helpers.hpp OpenSSL smart-pointer helpers for C++ tests.
provider-tests/cpp/utils/keygen_helpers.hpp EC/RSA session key helper routines for C++ EVP tests.
provider-tests/cpp/tests.rs Rust libtest-mimic harness to discover/run the C++ gtests.
provider-tests/cpp/smoke_tests.cpp Basic provider load/name smoke tests.
provider-tests/cpp/rsa_session_sign_verify_tests.cpp RSA session-key sign/verify (PKCS#1 + PSS) tests.
provider-tests/cpp/rsa_session_encrypt_decrypt_tests.cpp RSA session-key OAEP encrypt/decrypt tests.
provider-tests/cpp/ecdh_session_keyexch_tests.cpp ECDH session-key derive tests (buffer + file + negative).
provider-tests/cpp/ec_session_sign_verify_tests.cpp EC session-key sign/verify tests across curves + negatives.
provider-tests/cpp/CMakeLists.txt CMake build for the C++ gtest binary with pinned OpenSSL prefix.
provider-tests/build.rs Cargo build script driving the CMake build of C++ tests.
provider-tests/Cargo.toml Declares the provider-tests crate and its custom test target.
plugins/ossl_prov/src/azihsm_ossl_signature_rsa.c Extends RSA-PSS saltlen handling to cover “auto”.
integration-tests/testfiles/rsa/verify/verify_wrong_key.sh Adds RSA wrong-key verification negative case.
integration-tests/testfiles/rsa/verify/verify_negative_tampered_data.sh Adds RSA tampered-data verification negative case.
integration-tests/testfiles/rsa/verify/verify.sh RSA verify script under provider CLI tests.
integration-tests/testfiles/rsa/sign/sign.sh RSA sign script under provider CLI tests.
integration-tests/testfiles/rsa/rsa_pss_default_padding/round_trip.sh RSA-PSS default-padding round trip script.
integration-tests/testfiles/rsa/rsa-pss-specific/verify.sh RSA-PSS specific-parameter verify script.
integration-tests/testfiles/rsa/rsa-pss-specific/sign.sh RSA-PSS specific-parameter sign script.
integration-tests/testfiles/rsa/round_trip/round_trip.sh RSA sign/verify round trip script.
integration-tests/testfiles/rsa/pkcs1_encryption/pkcs1_encryption.sh RSA PKCS#1 encryption/decryption script.
integration-tests/testfiles/rsa/oneshot_verify/oneshot_verify.sh RSA one-shot verify (pkeyutl) script.
integration-tests/testfiles/rsa/oneshot_sign/oneshot_sign.sh RSA one-shot sign (pkeyutl) script.
integration-tests/testfiles/rsa/oneshot_round_trip/oneshot_round_trip.sh RSA one-shot sign+verify round trip script.
integration-tests/testfiles/rsa/oaep_encryption/oaep_encryption.sh RSA OAEP encryption/decryption script.
integration-tests/testfiles/rsa/import_key/import_key_negative_invalid_path.sh RSA import negative test for missing input key file.
integration-tests/testfiles/rsa/import_key/import_key.sh RSA import test for masked key creation/loading.
integration-tests/testfiles/rsa/default_padding/round_trip.sh RSA default-padding sign/verify round trip script.
integration-tests/testfiles/rsa/certificate/certificate.sh RSA certificate generation script via provider key.
integration-tests/testfiles/env.sh Shared environment setup for bash-based integration scripts.
integration-tests/testfiles/ec/verify/verify_negative_wrong_key.sh EC wrong-key verification negative case.
integration-tests/testfiles/ec/verify/verify_negative_tampered_data.sh EC tampered-data verification negative case.
integration-tests/testfiles/ec/verify/verify.sh EC verify script under provider CLI tests.
integration-tests/testfiles/ec/verify/oneshot_verify.sh EC one-shot verify (pkeyutl) script.
integration-tests/testfiles/ec/sign/sign.sh EC sign script under provider CLI tests.
integration-tests/testfiles/ec/sign/oneshot_sign.sh EC one-shot sign (pkeyutl) script.
integration-tests/testfiles/ec/round_trip/round_trip_import.sh EC import + sign/verify round trip script.
integration-tests/testfiles/ec/round_trip/round_trip.sh EC sign/verify round trip script.
integration-tests/testfiles/ec/import_key_sec1/import_key_sec1.sh EC SEC1 import script.
integration-tests/testfiles/ec/import_key/import_key_negative_invalid_path.sh EC import negative test for missing input key file.
integration-tests/testfiles/ec/import_key/import_key.sh EC import test for masked key creation/loading.
integration-tests/testfiles/ec/hmac/hmac.sh ECDH→HKDF-derived HMAC compute script.
integration-tests/testfiles/ec/hkdf_key_derivation/hmac_key_derivation.sh HKDF derivation for HMAC key material script.
integration-tests/testfiles/ec/hkdf_key_derivation/hkdf_key_derivation.sh HKDF derivation for AES key material script.
integration-tests/testfiles/ec/ecdh_key_exchange/ecdh_key_exchange.sh ECDH derive-to-file script.
integration-tests/testfiles/ec/ecdh_hkdf_hmac_roundtrip/ecdh_hkdf_hmac_roundtrip.sh ECDH→HKDF→HMAC round trip script.
integration-tests/testfiles/ec/create_key/create_key_negative.sh EC create-key negative (invalid curve) script.
integration-tests/testfiles/ec/create_key/create_key.sh EC create-key script with session/usage variations.
integration-tests/testfiles/ec/certificate/certificate.sh EC certificate generation script via provider key.
integration-tests/testfiles/digest/digest.sh Digest correctness script vs default provider.
integration-tests/src/lib.rs Rust test runner invoking lit across script directories/variants.
integration-tests/Cargo.toml Declares the integration-tests crate and dependencies.
integration-tests/.gitignore Ignores integration test artifacts produced by scripts.
Cargo.toml Adds integration-tests and provider-tests to the workspace.
.github/workflows/rust.yml Adds a CI job to build OpenSSL/provider and run both integration suites.
.cargo/audit.toml Ignores specific RustSec advisories for test-only transitive deps.

@mhatrevi mhatrevi added the ossl label Mar 4, 2026
@jenstopp jenstopp force-pushed the test/api-integration-test branch from c068c98 to a80f71d Compare March 7, 2026 09:24
Copilot AI review requested due to automatic review settings March 9, 2026 07:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 59 changed files in this pull request and generated 3 comments.

Copilot AI review requested due to automatic review settings March 9, 2026 16:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 59 changed files in this pull request and generated 4 comments.

@jenstopp jenstopp force-pushed the test/api-integration-test branch from a37f841 to 201b89b Compare March 9, 2026 16:20
Copilot AI review requested due to automatic review settings March 9, 2026 16:43
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 21 out of 59 changed files in this pull request and generated 2 comments.

@jenstopp jenstopp marked this pull request as ready for review March 9, 2026 17:00
Copy link
Copy Markdown
Contributor

@rajesh-gali rajesh-gali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please update readme with instructions to run the tests

@jenstopp jenstopp force-pushed the test/api-integration-test branch from 72458d6 to 63efd74 Compare March 12, 2026 15:30
Copilot AI review requested due to automatic review settings March 12, 2026 18:49
@jenstopp jenstopp force-pushed the test/api-integration-test branch from 63efd74 to 8812d80 Compare March 12, 2026 18:49
@jenstopp jenstopp force-pushed the test/api-integration-test branch from 8812d80 to 4e60127 Compare March 12, 2026 18:50
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 65 changed files in this pull request and generated 1 comment.


You can also share your feedback on Copilot code review. Take the survey.

Signed-off-by: Jens Topp <jens.topp@9elements.com>
Signed-off-by: Jens Topp <jens.topp@9elements.com>
Signed-off-by: Jens Topp <jens.topp@9elements.com>
Signed-off-by: Jens Topp <jens.topp@9elements.com>
Signed-off-by: Jens Topp <jens.topp@9elements.com>
@jenstopp jenstopp force-pushed the test/api-integration-test branch from 1f307aa to 80b6989 Compare March 13, 2026 21:51
Signed-off-by: Jens Topp <jens.topp@9elements.com>
Copilot AI review requested due to automatic review settings March 14, 2026 12:07
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 66 changed files in this pull request and generated 2 comments.


You can also share your feedback on Copilot code review. Take the survey.

Signed-off-by: Jens Topp <jens.topp@9elements.com>
Signed-off-by: Jens Topp <jens.topp@9elements.com>
Signed-off-by: Jens Topp <jens.topp@9elements.com>
Copilot AI review requested due to automatic review settings March 16, 2026 11:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 67 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

plugins/ossl_prov/integration-tests/openssl-cli/testfiles/env.sh:74

  • This script writes credentials_id.bin / credentials_pin.bin into $AZIHSM_KEY_DIR, but the provider’s documented fallback is to read these files from the current working directory (e.g. ./credentials_id.bin). The surrounding comments still describe a CWD-based fallback, which is no longer accurate for the CLI scripts unless they run with CWD set to $AZIHSM_KEY_DIR or the files are copied/symlinked there. Consider updating the comments (or placing the fallback files in CWD) to match actual behavior.
    .github/workflows/rust.yml:133
  • CI now builds OpenSSL with shared libraries and builds azihsm_api_native without OPENSSL_STATIC=1 / no-shared -fvisibility=hidden, but plugins/ossl_prov/README.md’s build instructions state libazihsm_api_native.so must be built against a static OpenSSL to avoid a circular dependency when the provider is loaded by libcrypto. Please either (a) restore the static-OpenSSL build flags/env in this workflow, or (b) update the README/build guidance to reflect the new supported shared-OpenSSL approach.
        ./Configure --prefix=/opt/openssl-3.0.3 --libdir=lib \
          -fPIC
        make -j"$(nproc)"
        sudo make install_sw

    - name: Build azihsm with OpenSSL
      env:
        OPENSSL_DIR: /opt/openssl-3.0.3
      run: |
        cargo build -p azihsm_api_native --features mock
        cargo build -p azihsm_ossl_provider --features mock


You can also share your feedback on Copilot code review. Take the survey.

Signed-off-by: Jens Topp <jens.topp@9elements.com>

[[test]]
harness = false
name = "capi_gtest_runner"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we rename the binary to something "openssl_capi_integration_tests" to align with other executables.

Signed-off-by: Jens Topp <jens.topp@9elements.com>
Copilot AI review requested due to automatic review settings March 16, 2026 21:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 66 out of 67 changed files in this pull request and generated 3 comments.


You can also share your feedback on Copilot code review. Take the survey.

Signed-off-by: Jens Topp <jens.topp@9elements.com>
@jenstopp
Copy link
Copy Markdown
Collaborator Author

@rajesh-gali here I tried to write up the problem I was facing during the development of the test suite (CLI and CAPI) with usage of the config file

Provider double-init / deadlock: the core problem and how the test suites avoid it

What activate = 1 does in openssl.cnf

The config has activate = 1 in the [azihsm_sect] section. This tells libcrypto to load and initialize the provider immediately during config processing — the application never calls OSSL_PROVIDER_load() itself. The internal call chain inside libcrypto:

OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, ...)
  → ossl_init_config_settings()                        // RUN_ONCE macro
    → ossl_config_int()
      → CONF_modules_load_file()                       // reads OPENSSL_CONF env var
        → provider_conf_load()                         // checks activate=1
          → ossl_provider_activate()
            → provider_init()
              → DSO_load() + DSO_bind_func("OSSL_provider_init")
                → provider's OSSL_provider_init()      // entry point in azihsm_provider.so

Config loading is triggered either by an explicit OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, ...) call, or implicitly when provider operations (e.g. ossl_provider_find()) trigger config loading internally. The openssl CLI binary calls it explicitly during startup.

Without activate = 1, the section just declares the provider — it's only loaded when explicitly requested. With it, provider_conf_load() checks the activate key and calls ossl_provider_activate() to load the provider immediately. This is the standard way to make a provider available to CLI tools like openssl genpkey, which don't have their own code to call OSSL_PROVIDER_load().

The core problem

When OpenSSL's default library context auto-loads this config (triggered by the OPENSSL_CONF env var), it loads and initializes the azihsm provider into the default context.

The deadlock occurs when the provider's code uses OpenSSL API functions internally (EVP_MD_fetch, crypto primitives, etc.). If the provider embeds its own copy of libcrypto (static linking), that second libcrypto instance auto-initializes on first use — sees OPENSSL_CONF in the environment, reads the config, encounters activate = 1, and attempts to load the provider again. Recursive init, deadlock.

The provider doesn't explicitly call OpenSSL initialization — it's the auto-init mechanism in the statically-linked libcrypto copy that triggers it. Any OpenSSL API call from the provider is enough to start the chain.

How each suite avoids it

CLI tests — shared OpenSSL (single libcrypto)

With shared linking, the openssl binary and azihsm_provider.so share the same libcrypto.so.3 via the dynamic linker. The dynamic linker loads it once. When the binary's libcrypto reads openssl.cnf and loads the provider, the provider's calls into libcrypto go to the already-initialized single instance — no second auto-init, no recursion.

This is why the CI deadlocked before: OpenSSL was built with no-shared -fvisibility=hidden, creating two separate static copies of libcrypto. The fix was building OpenSSL with shared libraries (.github/workflows/rust.yml).

CAPI tests — OPENSSL_INIT_NO_LOAD_CONFIG + explicit context

The CAPI tests use a two-layer defense:

  1. OPENSSL_init_crypto(OPENSSL_INIT_NO_LOAD_CONFIG, NULL) (plugins/ossl_prov/integration-tests/openssl-capi/cpp/main.cpp:13) — suppresses automatic config loading on the default context. This skips the config loading step entirely (runs ossl_init_no_config() instead, which marks config as "done" without loading any file), so even though OPENSSL_CONF is set, the default context never reads it and the provider is never loaded into the default context.

  2. OSSL_LIB_CTX_new() + OSSL_LIB_CTX_load_config() (plugins/ossl_prov/integration-tests/openssl-capi/cpp/utils/provider_ctx.hpp:25-44) — each test creates a dedicated, non-default library context and explicitly loads the config into it. The provider loads into this isolated context only. Since the default context never loaded the provider, there's no double-init.

Summary

Suite Strategy Why no double-init
CLI Shared libcrypto One libcrypto.so.3 instance; provider calls re-enter the same already-initialized library — no second auto-init possible
CAPI NO_LOAD_CONFIG + per-test OSSL_LIB_CTX Default context never loads provider; config loaded explicitly into isolated contexts — safe regardless of static/shared linking

The CAPI approach is inherently safe regardless of static/shared linking because it never lets the default context auto-load the provider. The CLI approach depends on shared linking to avoid the recursive init.


Annotated call stacks

CLI test suite

1. Rust test runner (nextest) invokes bash script
   e.g. plugins/ossl_prov/integration-tests/openssl-cli/testfiles/ec/round_trip/round_trip.sh

2. Script sources env.sh
   plugins/ossl_prov/integration-tests/openssl-cli/testfiles/env.sh
   ├── Generates key material in $REPO_ROOT/target/test-keymat/cli/     (lines 59-87)
   ├── Generates openssl.cnf with activate=1 for azihsm provider        (lines 95-119)
   │   └── module = <absolute path to azihsm_provider.so>
   │   └── activate = 1
   └── export OPENSSL_CONF="$AZIHSM_KEY_DIR/openssl.cnf"                (line 121)

3. Script calls $OPENSSL_BIN (e.g. genpkey, dgst)
   └── openssl binary starts
       └── libcrypto.so.3 config loading (OPENSSL_INIT_LOAD_CONFIG)
           └── reads OPENSSL_CONF
               └── activate=1 → OSSL_PROVIDER_load("azihsm")
                   └── dlopen(azihsm_provider.so)
                       └── OSSL_provider_init()
                           └── provider uses EVP_* calls → same libcrypto.so.3
                               (shared linking: single instance, already initialized,
                                no recursion)

4. openssl CLI executes the cryptographic operation (genpkey/dgst/req)
   using the now-loaded azihsm provider via -propquery "?provider=azihsm"

Key requirement: OpenSSL must be built with shared libraries so the binary and provider share one libcrypto.so.3. CI builds OpenSSL with ./Configure --prefix=/opt/openssl-3.0.3 --libdir=lib -fPIC (.github/workflows/rust.yml:122-123).

CAPI test suite

1. Rust test runner discovers gtest binary and enumerates tests
   plugins/ossl_prov/integration-tests/openssl-capi/cpp/openssl_capi_integration_tests.rs
   ├── Generates key material in $REPO_ROOT/target/test-keymat/capi/     (generate_dev_key_material())
   ├── Generates openssl.cnf with activate=1                             (generate_openssl_conf())
   └── Calls gtest binary with --gtest_list_tests, parses output         (parse_gtest_list())
       └── Passes OPENSSL_CONF to each subprocess via .env()             (lines 336, 408)

2. For each test, Rust runner invokes the gtest binary with --gtest_filter=Suite.Test

3. gtest binary main() runs BEFORE any test
   plugins/ossl_prov/integration-tests/openssl-capi/cpp/main.cpp:13
   └── OPENSSL_init_crypto(OPENSSL_INIT_NO_LOAD_CONFIG, NULL)
       └── Marks default context: SKIP config auto-loading
           (OPENSSL_CONF is in env but default context will never read it)

4. Individual test creates ProviderCtx (RAII)
   plugins/ossl_prov/integration-tests/openssl-capi/cpp/utils/provider_ctx.hpp:23-68
   ├── OSSL_LIB_CTX_new()                                                (line 25)
   │   └── Creates a fresh, isolated library context (not the default)
   ├── std::getenv("OPENSSL_CONF")                                       (line 35)
   └── OSSL_LIB_CTX_load_config(libctx_, conf)                           (line 44)
       └── Loads config INTO the isolated context only
           └── activate=1 → OSSL_PROVIDER_load("azihsm") into libctx_
               └── Provider initializes in isolated context
                   └── Provider's OpenSSL API calls use the process-wide libcrypto
                       but the DEFAULT context was never loaded with provider config
                       → no recursive activation, no deadlock

5. Test runs cryptographic operations against ProviderCtx::libctx()
   e.g. EVP_PKEY_CTX_new_from_name(libctx, "EC", "?provider=azihsm")

6. ProviderCtx destructor (~ProviderCtx)                                 (line 70-76)
   └── OSSL_LIB_CTX_free(libctx_)
       └── Unloads provider, releases all resources for that context

Key requirement: OPENSSL_INIT_NO_LOAD_CONFIG must be called before any other OpenSSL API use. This is why it's in main() before InitGoogleTest. The per-test OSSL_LIB_CTX isolation also ensures tests don't interfere with each other.

@walterchris
Copy link
Copy Markdown
Collaborator

Closed with respect to #266

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OSSL test suite for testing with session keys

5 participants